package ch.ffhs.services;

import java.io.IOException;
import java.util.ArrayList;

import ch.ffhs.contacts.Contact;
import ch.ffhs.contacts.ContactsAlarmListAdapter;
import ch.ffhs.contacts.ContactsListAdapter;
import ch.ffhs.activities.AlarmActivity;
import ch.ffhs.activities.AlarmNotification;
import ch.ffhs.activities.MainActivity;
import ch.ffhs.activities.R;
import ch.ffhs.services.MessageService.MessageBinder;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;

/**
 * class ServiceManager. Implements central handling of any services, data- and
 * alarm-handling.
 * 
 * @author mf
 */
public class ServiceManager {

	/**
	 * indicates, if the app is in testing-mode
	 */
	public static Boolean testMode = true;

	/**
	 * static instance of service-manager
	 */
	public static ServiceManager currentServiceManager = null;

	private Boolean isRunning = false;
	private Boolean statusActive = false;
	private Boolean modusDay = true;
	private Integer sensorSensitivity = 0;
	private MainActivity main = null;
	private SharedPreferences prefs;

	// contacts
	private ArrayList<Contact> contacts = new ArrayList<Contact>();
	/**
	 * List-adapter for contacts-list on main activity
	 */
	public ContactsListAdapter contactListAdapter;
	/**
	 * List-adapter for contacts-list on alarm activity
	 */
	public ContactsAlarmListAdapter contactAlarmListAdapter;

	// countdown
	private Integer timecount = 0;
	private Integer countdown = 0;

	// webservice
	private ServerWebservice serverWS;

	// gps
	private GpsPosition gpsPosition;

	// ServiceManager
	/**
	 * constructor of ServiceManager. Initializes settings, contact-lists, GPS
	 * and server-connection.
	 * 
	 * @param main
	 *            instance of class MainActivity
	 */
	public ServiceManager(MainActivity main) {
		this.main = main;
		currentServiceManager = this;

		// load defaults
		PreferenceManager
				.setDefaultValues(main, R.xml.activity_settings, false);
		prefs = PreferenceManager.getDefaultSharedPreferences(main);

		// setup contacts-list
		contactListAdapter = new ContactsListAdapter(main, contacts);
		contactAlarmListAdapter = new ContactsAlarmListAdapter(main, contacts);

		// load gps
		gpsPosition = new GpsPosition(main);

		// load webservice
		serverWS = new ServerWebservice(main, gpsPosition);
	}

	/**
	 * starting service-manager. If app is active, it starts monitoring.
	 */
	public void start() {
		View btnRestart = (View) main.findViewById(R.id.btnRestart);
		btnRestart.setVisibility(View.INVISIBLE);

		// starting service and countdown
		if (statusActive || !isRunning) {
			Log.d("ServiceManager", "Start");
			// reset countdown
			setCountdown(timecount);
			setAlarmProgress(timecount, timecount);
			startMotionSensorService();
			isRunning = true;
		}
	}

	/**
	 * stops service-manager. If monitoring is running, it stops monitoring.
	 */
	public void stop() {
		if (isRunning) {
			Log.d("ServiceManager", "Stopp");

			// stop MotionSensorService
			stopMotionSensorService();

			// stop MessageService
			stopMessageService();

			isRunning = false;
		}
	}

	private void setCountdown(Integer count) {
		countdown = count;
		setAlarmProgress(timecount, countdown);

		if (countdown == 0) {
			// activate alarm
			startAlarm();
		}
	}

	// MotionSensorService
	private MotionSensorService.MotionSensorBinder mMotionSensorBinder = null;
	private boolean isMotionSensorServiceActive = false;

	@SuppressLint("HandlerLeak")
	private final Handler mMotionSensorCallbackHandler = new Handler() {
		public void handleMessage(Message msg) {
			Log.d("mMotionSensorCallbackHandler", "handleMessage");
			final Bundle bundle = msg.getData();
			setCountdown(bundle.getInt("Countdown"));
		}
	};

	private ServiceConnection mMotionSensorServiceConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder binder) {
			Log.d("ServiceConnection", "onServiceConnected: Coundown "
					+ Integer.toString(timecount));
			mMotionSensorBinder = (MotionSensorService.MotionSensorBinder) binder;
			// init countdown
			mMotionSensorBinder.setCountdown(timecount);
			mMotionSensorBinder.setSensitivity(sensorSensitivity);
			mMotionSensorBinder
					.setCallbackHandler(mMotionSensorCallbackHandler);
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			mMotionSensorBinder = null;
		}
	};

	private void startMotionSensorService() {
		if (statusActive && !isMotionSensorServiceActive) {

			Log.d("MainActivity", "startMotionSensorService");
			final Intent mMotionSensorIntent = new Intent(main,
					ch.ffhs.services.MotionSensorService.class);

			main.bindService(mMotionSensorIntent,
					mMotionSensorServiceConnection, Context.BIND_AUTO_CREATE);
			if (main.startService(mMotionSensorIntent) == null) {
				Log.d("MainActivity", "onCreate - start service unsuccessful");
			} else {
				isMotionSensorServiceActive = true;
			}
		}
	}

	private void stopMotionSensorService() {
		Log.d("MotionSensorService", "Stopp");
		if (isMotionSensorServiceActive) {
			mMotionSensorCallbackHandler.removeCallbacksAndMessages(null);

			final Intent mMotionSensorIntent = new Intent(main,
					MotionSensorService.class);
			if (mMotionSensorServiceConnection != null) {
				main.unbindService(mMotionSensorServiceConnection);
			}
			main.stopService(mMotionSensorIntent);
			isMotionSensorServiceActive = false;
		}
	}

	// MotionSensorService

	// MessageService
	private MessageService.MessageBinder mMessageBinder = null;
	private boolean isMessageServiceActive = false;

	@SuppressLint("HandlerLeak")
	private final Handler mMessageCallbackHandler = new Handler() {
		public void handleMessage(Message msg) {
			Log.d("mMessageCallbackHandler", "handleMessage");
			final Bundle bundle = msg.getData();
			if (bundle.containsKey("AlarmDeactivated")) {
				final boolean alarmDeactivated = bundle
						.getBoolean("AlarmDeactivated");
				if (alarmDeactivated) {
					// deactivate alarm
					stopAlarm();
				}
			} else if (bundle.containsKey("SendMessage")) {
				final String sendMessage = bundle.getString("SendMessage");
				Toast.makeText(main, sendMessage, Toast.LENGTH_LONG).show();

				AlarmNotification.updateNotification(main
						.getString(R.string.alarm_notification_title), main
						.getString(R.string.alarm_notification_message_sent),
						AlarmNotification.ALARM_NOTIFICATION);

				final Integer messageCount = bundle.getInt("MessageCount");
				switch (messageCount) {
				case 1:
					submitState(ALARM_LEVEL_MESSAGE1);
					break;
				case 2:
					submitState(ALARM_LEVEL_MESSAGE2);
					break;
				case 3:
					submitState(ALARM_LEVEL_MESSAGE3);
					break;
				case 4:
					submitState(ALARM_LEVEL_MESSAGE4);
					break;
				}
				Contact contact = contacts.get(messageCount - 1);
				if (contact != null) {
					contact.setChecked(true);
					contactAlarmListAdapter.notifyDataSetChanged();
				}
			}
		}
	};

	private ServiceConnection mMessageServiceConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder binder) {
			mMessageBinder = (MessageBinder) binder;
			mMessageBinder.setContacts(contacts);
			mMessageBinder.setGpsPosition(gpsPosition);
			mMessageBinder.setCallbackHandler(mMessageCallbackHandler);
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			mMessageBinder = null;
		}
	};

	private void startMessageService() {
		if (statusActive && !isMessageServiceActive) {
			Log.d("MainActivity", "startMessageService");
			final Intent mMessageIntent = new Intent(main,
					ch.ffhs.services.MessageService.class);
			main.bindService(mMessageIntent, mMessageServiceConnection,
					Context.BIND_AUTO_CREATE);
			if (main.startService(mMessageIntent) == null) {
				Log.d("MainActivity", "startMessageService unsuccessful");
			} else {
				isMessageServiceActive = true;
			}
		}
	}

	private void stopMessageService() {
		Log.d("MainActivity", "stopMessageService");
		if (isMessageServiceActive) {
			final Intent mMessageIntent = new Intent(main,
					ch.ffhs.services.MessageService.class);
			main.unbindService(mMessageServiceConnection);
			main.stopService(mMessageIntent);
			isMessageServiceActive = false;
		}
	}

	// MessageService

	/**
	 * request-code for startActivityForResult()
	 */
	public static final int ALARM_REQUEST = 1;

	/**
	 * starts an alarm-process. displays alarm-activity and notification in
	 * status-bar and plays an alarm-sound.
	 */
	public void startAlarm() {
		// stop MotionSensorService
		stopMotionSensorService();

		// display alarm
		Intent alarm = new Intent(main, AlarmActivity.class);
		main.startActivityForResult(alarm, ALARM_REQUEST);
		// add notification
		AlarmNotification.displayNotification(main, alarm,
				main.getString(R.string.alarm_notification_title),
				main.getString(R.string.alarm_notification_message),
				AlarmNotification.ALARM_NOTIFICATION);
		// play alarm
		String ringtone = prefs
				.getString("settingRingtone", "default ringtone");
		Uri uri = Uri.parse(ringtone);
		// Uri ringtone = RingtoneManager
		// .getDefaultUri(RingtoneManager.TYPE_ALARM);
		playSound(main, uri);

		// start MessageSerive
		startMessageService();

		submitState(ALARM_LEVEL_SOUND);
	}

	/**
	 * stops an active alarm-process.
	 */
	public void stopAlarm() {
		stopMessageService();

		View btnRestart = (View) main.findViewById(R.id.btnRestart);
		btnRestart.setVisibility(View.VISIBLE);
		mMediaPlayer.stop();
				
		submitState(ALARM_LEVEL_NONE);
	}

	public void stopAlarmFromSms() {
		main.finishActivity(ALARM_REQUEST);
		stopAlarm();
	}
	
	private MediaPlayer mMediaPlayer;

	private void playSound(Context context, Uri uri) {
		mMediaPlayer = new MediaPlayer();
		try {
			mMediaPlayer.setDataSource(context, uri);
			final AudioManager audioManager = (AudioManager) context
					.getSystemService(Context.AUDIO_SERVICE);
			int maxVolume = audioManager
					.getStreamMaxVolume(AudioManager.STREAM_ALARM);
			mMediaPlayer.setVolume(maxVolume, maxVolume);
			if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
				mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
				mMediaPlayer.prepare();
				mMediaPlayer.start();
			}
		} catch (IOException e) {
			System.out.println("OOPS");
		}
	}

	/**
	 * loads the settings from sharedPreferences
	 */
	public void loadSettings() {
		statusActive = prefs.getBoolean("settingActive", false);

		testMode = prefs.getBoolean("settingTestmode", true);

		sensorSensitivity = Integer.parseInt(prefs.getString(
				"settingSensorSensitivity", "5"));

		if (modusDay) {
			timecount = Integer.parseInt(prefs.getString("settingDayTimecount",
					"15"));
		} else {
			timecount = Integer.parseInt(prefs.getString(
					"settingNightTimecount", "60"));
		}
		setStatusText((Switch) main.findViewById(R.id.switchModus),
				this.statusActive);

		submitState(ALARM_LEVEL_NONE);

		// load contacts
		contacts.clear();
		Contact contact1 = new Contact(main, "settingsContact1");
		contacts.add(contact1);
		Contact contact2 = new Contact(main, "settingsContact2");
		contacts.add(contact2);
		Contact contact3 = new Contact(main, "settingsContact3");
		contacts.add(contact3);
		Contact contact4 = new Contact(main, "settingsContact4");
		contacts.add(contact4);
		// update contacts-list
		contactListAdapter.notifyDataSetChanged();
		contactAlarmListAdapter.notifyDataSetChanged();
	}

	private void setAlarmProgress(int maxTime, int timeToAlarm) {
		TextView progressText = (TextView) main
				.findViewById(R.id.txtProgressAlarmText);
		progressText.setText(main.getString(R.string.app_progress_text) + " "
				+ Integer.toString(timeToAlarm) + " "
				+ main.getString(R.string.app_progress_text_unit));

		ProgressBar progressBar = (ProgressBar) main
				.findViewById(R.id.progressAlert);
		progressBar.setProgress(timeToAlarm);
		progressBar.setMax(maxTime);
	}

	private void setStatusText(Switch switchModus, boolean active) {
		if (active) {
			switchModus.setTextColor(Color.BLACK);
			if (switchModus.isChecked()) {
				switchModus.setText(R.string.app_modus_active_day);
			} else {
				switchModus.setText(R.string.app_modus_active_night);
			}
		} else {
			switchModus.setText(R.string.app_modus_inactive);
			switchModus.setTextColor(Color.RED);
		}
	}

	/**
	 * sets the app-modus to day or night
	 * 
	 * @param isDay
	 *            true, if active modus is day
	 */
	public void setModus(boolean isDay) {
		this.modusDay = isDay;
	}

	/**
	 * alarm-level none. no alarm-process active.
	 */
	public static final Integer ALARM_LEVEL_NONE = 0;
	/**
	 * alarm-level sound. alarm-process is active, alarm is displaying and
	 * alarm-sound is playing.
	 */
	public static final Integer ALARM_LEVEL_SOUND = 1;
	/**
	 * alarm-level message 1. alarm-process is active, message to
	 * emergency-contact 1 is sent.
	 */
	public static final Integer ALARM_LEVEL_MESSAGE1 = 2;
	/**
	 * alarm-level message 2. alarm-process is active, message to
	 * emergency-contact 2 is sent.
	 */
	public static final Integer ALARM_LEVEL_MESSAGE2 = 3;
	/**
	 * alarm-level message 3. alarm-process is active, message to
	 * emergency-contact 3 is sent.
	 */
	public static final Integer ALARM_LEVEL_MESSAGE3 = 4;
	/**
	 * alarm-level message 4. alarm-process is active, message to
	 * emergency-contact 4 is sent.
	 */
	public static final Integer ALARM_LEVEL_MESSAGE4 = 5;

	/**
	 * submit the actual state of the app to the monitoring-server.
	 * 
	 * @param alarmLevel
	 *            provide the actual alarm-level
	 */
	public void submitState(Integer alarmLevel) {
		if (serverWS != null) {
			serverWS.setActive(this.statusActive);
			serverWS.setDayModus(this.modusDay);
			serverWS.setAlarmLevel(alarmLevel);
			serverWS.submitState();
		}
	}
}
